-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add support for property initialization during cloning #6538
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests are a little sparse 🙂 Some things I could think of:
- Test that assigning the wrong type will trigger a type error
- Test accessibility modifiers still work
- Test that it works from outside the class (if that is intended)
Another issue is that publicly exposed properties are very rare in the PHP ecosystem at the moment because we do not have asymmetric visibility. For any property that requires calling a setter this feature will not be usable.
Full disclosure, I'm not sure I'm in support for this RFC if we solely introduce it for initonly
as (IMO) it seems like a solution to a non-problem. initonly
is a locked property, if it needs to change, from the inside or outside, asymmetric visibility is more fitting. We don't have that yet but that doesn't mean we should make initonly
do stuff it's not meant to do.
I'm of decidedly mixed feelings here.
Point 7 is the really big one, as it means you either have to risk invalid state, or build methods around everything to the point that you wouldn't get much benefit anyway. On its own, if it followed scoping rules on private/protected (you can't clone-with a private property from outside the object, but you can within the object), it would make with-er ethods easier and single-expression, thus making them compatible with short-functions. Those are all wins in my book, so I'm in favor generally. It's The alternative here would be to allow passing potentially named properties to the |
Yeah, that's just because it was extremely late when I finished the initial implementation. And I didn't want to put even more effort into the PR yet, until I hear some initial feedback. But nevertheless, I also planned to add such tests! P.S. I've just added a test locally if visibility rules apply, and it worked well (as originally imagined). :)
Yes, I think it's chainable, the as far as I saw
Yeah, my intention is to make "clone with" a special case when I propose initonly.
In general, I also agree with the superiority of defined properties, but I'm not sure if forbidding dynamic properties should be in the scope of "clone with". Nevertheless, we can discuss it with a broader audience (e.g. on list or in chat).
That's ok, and let's talk about it when discussing the
This is an interesting approach, but I'm not sure it would work well in practice. My main concern - besides that it requires lots of boilerplate, basically a second constructor - is that you can't (currently) distinguish a nullable property from a property which you don't want to initialize after cloning. Besides, I'm not sure how the parameter list of this method would look like? Or this would be an exempt from variance rules (just like a constructor)? |
8ceaa77
to
dce4955
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how I feel about this - it'd be useful with initonly properties.
Without initonly properties, it's convenient syntactic sugar, but may not be used often in practice
ZVAL_DEREF(value); | ||
} | ||
|
||
if (OP2_TYPE == IS_CONST) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like OP2_TYPE is always const in the code generated by zend_compile.c? Is extending this to dynamic property names an open question?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, it shouldn't be, only OP1 should be always a constant. I've just added two tests to support my claims :) They'll ensure that op1 is a const, and op2 can be an expression.
Zend/zend_ast.c
Outdated
@@ -2079,6 +2084,12 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio | |||
ast = ast->child[1]; | |||
goto tail_call; | |||
|
|||
case ZEND_AST_INITIALIZER_EXPR: | |||
smart_str_append(str, zend_ast_get_str(ast->child[0])); | |||
smart_str_appends(str, "= "); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can merge with ZEND_AST_NAMED_ARG
smart_str_appends(str, "= "); | |
smart_str_appends(str, ": "); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for spotting this! I originally used =
in the initializer expression syntax, so this one stuck here from that time.
@@ -86,10 +37,25 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { | |||
REGISTER_LONG_CONSTANT("T_CONSTANT_ENCAPSED_STRING", T_CONSTANT_ENCAPSED_STRING, CONST_CS | CONST_PERSISTENT); | |||
REGISTER_LONG_CONSTANT("T_STRING_VARNAME", T_STRING_VARNAME, CONST_CS | CONST_PERSISTENT); | |||
REGISTER_LONG_CONSTANT("T_NUM_STRING", T_NUM_STRING, CONST_CS | CONST_PERSISTENT); | |||
REGISTER_LONG_CONSTANT("T_INCLUDE", T_INCLUDE, CONST_CS | CONST_PERSISTENT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the new sorting order based on?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just run the tokenizer_data_gen.sh
script ^^ as I thought that everyone does this, but then I realized there are too many changes... That said, I'm ok to revert this if this script shouldn't be used. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think someone may have changed the order of the tokenizer constants without being aware of this script. It may be useful to run that script in a separate PR anyway if we plan to keep using it.
I didn't notice that that file was created by a script
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think they may have been reordered in b03cafd which didn't update tokenizer_data.c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll create a separate PR for claiming back the original token order.
@@ -1095,6 +1095,150 @@ ZEND_VM_C_LABEL(assign_op_object): | |||
ZEND_VM_NEXT_OPCODE_EX(1, 2); | |||
} | |||
|
|||
ZEND_VM_HANDLER(200, ZEND_INIT_OBJ, VAR, CONST, CACHE_SLOT, SPEC(OP_DATA=CONST|TMP|VAR|CV)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to only support clone $variable with {...}
.
I assume there's more work planned to support clone function_result() with {...}
(TMP/TMPVAR)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I wasn't aware of this, since it's my first time when I touch the VM. Yeah, function_result()
should work as well, so I'll need to work on this for sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so I've just tested (and just pushed the change) that the clone operand can be an expression. Did you say so because of the VAR
op1? This is really something I'll have experiment more with. originally, I used TMPVAR
, but that didn't work (maybe because of unrelated reasons).
} else { | ||
ZEND_VM_C_LABEL(fast_assign_obj): | ||
value = zend_assign_to_variable(property_val, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES()); | ||
if (UNEXPECTED(RETURN_VALUE_USED(opline))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When/how would the return value be used? This seems to be for prop_name = expr
when the property is untyped
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, I only copy-pasted the handler of ZEND_ASSIGN_OBJ
and modified it only as much as necessary. So I'll definitely want to get rid of the unused parts a bit later.
{ | ||
return clone $this with { | ||
property1: 1, | ||
property2: "foo", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could add a test of this with function calls, e.g.
- If a previous property expression throws, subsequent ones are not evaluated?
- If an expression throws, type checks are not performed (e.g.
clone $this with { strTypedProperty: throw new Exception("test") }
) - temporary values such as
strtolower("ABC")
are freed if there's a type error assigning to a typed property
EDIT: Oh. So far, this is only implemented for constant operands in Zend/zend_vm_def.h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the ideas, I'll definitely add such tests!
dce4955
to
9a7a394
Compare
9a7a394
to
8928766
Compare
I agree that this construct would be the most useful for |
@kocsismate Same here right? 🙂 |
Yeah, I'd like to try to finish this as well, although the situation is a bit worse here as I don't know what the best approach would be to implement this. |
Closing in favor of #9497 |
The primary purpose of this RFC is to make cloning less verbose when multiple properties are assigned to afterwards, but it might be useful in case of
initonly
properties in the future.Given the following piece of code:
This RFC would allow to write: